#include <algorithm>
#include <cassert>
#include <cctype>
#include <fstream>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <vector>

#include <cqcppsdk/cqcppsdk.h>
#include <Windows.h>

using namespace cq;
using namespace std;

template <typename T>
inline void push(const T& data, int64_t group_id) {
    stringstream ss;
    ss << data;
    try {
        send_group_message(group_id, ss.str());
        logging::info_success("推送", "推送了1条消息");
    } catch (ApiError& e) {
        logging::error("推送", "推送失败，错误码：" + to_string(e.code));
    }
    return;
}

const string rawdata("Z:\\home\\user\\coolq\\curl\\dxy");

const regex province("\\{\"province.*?\\[*?\\]\\}");
const regex name("\"provinceShortName\":\".*?\"");
const regex confirmed("\"confirmedCount\":\\d+");
const regex cured("\"curedCount\":\\d+");
const regex dead("\"deadCount\":\\d+");

const regex news("\\{\"id[^<>]*?pub[^<>]*?title[^<>]*?summary.*?\\}");
const regex regex_title("\"title\":\".*?\"");
const regex regex_summary("\"summary\":\".*?\"");
const regex regex_source("\"infoSource\":\".*?\"");
const regex regex_url("\"sourceUrl\":\".*?\"");

inline string match(const string& str, const regex& rgx) {
    smatch sm;
    regex_search(str, sm, rgx);
    return sm.str();
}

inline string getstring(const string& str) {
    size_t r = str.rfind('"');
    size_t l = str.rfind('"', r - 1);
    string ret = str.substr(l + 1, r - l - 1);
    while (!ret.empty() && ret.back() >= -1 && isspace(ret.back())) ret.pop_back();
    if (ret.length() >= 2 && ret.substr(ret.length() - 2) == "\\n") ret = ret.substr(0, ret.length() - 2);
    while (!ret.empty() && ret.back() >= -1 && isspace(ret.back())) ret.pop_back();
    return ret;
}

inline int getnum(const string& str) {
    int ret = 0;
    for (size_t i = 0; i < str.length(); i++)
        if (isdigit(str[i])) ret = ret * 10 + str[i] - '0';
    return ret;
}

class province_data {
private:
    string areaname;
    int confirmedcount;
    int curedcount;
    int deadcount;

public:
    province_data(const string& str)
        : areaname(getstring(match(str, name))),
          confirmedcount(getnum(match(str, confirmed))),
          curedcount(getnum(match(str, cured))),
          deadcount(getnum(match(str, dead))) {
    }

    province_data(const string& _name, const int& _confirmed, const int& _cured, const int& _dead)
        : areaname(_name), confirmedcount(_confirmed), curedcount(_cured), deadcount(_dead) {
    }

    inline friend ostream& operator<<(ostream& out, const province_data& data) {
		out << "[数据更新]" << endl;
        out << "地区：" << data.areaname << endl;
        if (data.confirmedcount != -1) out << "确诊人数：" << data.confirmedcount << endl;
        if (data.curedcount != -1) out << "治愈人数：" << data.curedcount << endl;
        if (data.deadcount != -1) out << "死亡人数：" << data.deadcount;
        return out;
    }

    inline friend bool operator!=(const province_data& left, const province_data& right) {
        assert(left.areaname == right.areaname);
        return left.confirmedcount != right.confirmedcount || left.curedcount != right.curedcount
               || left.deadcount != right.deadcount;
    }

    inline friend province_data operator^(const province_data& newdata, const province_data& olddata) {
        assert(newdata.areaname == olddata.areaname);
        return province_data(newdata.areaname,
                             newdata.confirmedcount == olddata.confirmedcount ? -1 : newdata.confirmedcount,
                             newdata.curedcount == olddata.curedcount ? -1 : newdata.curedcount,
                             newdata.deadcount == olddata.deadcount ? -1 : newdata.deadcount);
    }
};

class news_data {
private:
    string title;
    string summary;
    string source;
    string url;

public:
    news_data(const string& str)
        : title(getstring(match(str, regex_title))),
          summary(getstring(match(str, regex_summary))),
          source(getstring(match(str, regex_source))),
          url(getstring(match(str, regex_url))) {
    }

    inline friend ostream& operator<<(ostream& out, const news_data& data) {
        out << "[新闻推送]" << endl;
        out << data.title << endl;
        out << data.summary << endl;
        out << "来源：" << data.source << endl;
        out << "链接：" << data.url;
        return out;
    }

    inline friend bool operator!=(const news_data& left, const news_data& right) {
        return left.url != right.url;
    }
};

class info_dxy {
private:
    vector<province_data> _patients;
    vector<news_data> _news;

public:
    info_dxy(const string& str) {
        auto begin = sregex_iterator(str.begin(), str.end(), province);
        auto end = sregex_iterator();
        for (sregex_iterator i = begin; i != end; i++) _patients.push_back(province_data(i->str()));
        begin = sregex_iterator(str.begin(), str.end(), news);
        for (sregex_iterator i = begin; i != end; i++) _news.push_back(news_data(i->str()));
    }

    info_dxy() {
    }

    inline void _push(int64_t group_id) const {
        for (const auto& i : _patients) push(i, group_id);
        for (const auto& i : _news) push(i, group_id);
        return;
    }

    inline friend info_dxy operator^(const info_dxy& newdata, const info_dxy& olddata) {
        info_dxy ret;
        assert(newdata._patients.size() == olddata._patients.size());
        for (size_t i = 0; i < newdata._patients.size(); i++)
            if (newdata._patients[i] != olddata._patients[i])
                ret._patients.push_back(newdata._patients[i] ^ olddata._patients[i]);
        for (size_t i = 0; i < newdata._news.size() && newdata._news[i] != olddata._news[0]; i++)
            ret._news.push_back(newdata._news[i]);
        return ret;
    }
};

template <>
inline void push(const info_dxy& data, int64_t group_id) {
    data._push(group_id);
    return;
}

CQ_INIT {
    on_enable([] { logging::info("启用", "插件已启用"); });
}

CQ_MENU(start) {
    logging::info("开始", "开始推送");
    info_dxy* cache = nullptr;
    for (; true; Sleep(25000)) {
        ifstream fin(rawdata);
        string raw;
        for (string buf; !fin.eof(); raw += buf) getline(fin, buf, (char)-1);
        logging::info("更新", "爬取信息");
        info_dxy* now = new info_dxy(raw);
        logging::info("更新", "更新信息");
        if (cache) {
            info_dxy* diff = new info_dxy(*now ^ *cache);
            delete cache;
            const auto GroupList = get_group_list();
            for (const auto& i : GroupList) push(*diff, i.group_id);
            delete diff;
        }
        cache = now;
    }
}
